/*

Copyright 2009 Nicola Dal Pont

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/
package com.astoolkit.workflow
{
	import avmplus.getQualifiedClassName;
	
	import com.astoolkit.workflow.constant.EMPTY_PIPELINE;
	import com.astoolkit.workflow.constant.EmptyPipelinePolicy;
	import com.astoolkit.workflow.constant.PIPELINE_OUTLET;
	import com.astoolkit.workflow.constant.UNDEFINED;
	import com.astoolkit.workflow.util.DataWrapper;
	
	import mx.binding.utils.BindingUtils;
	import mx.core.mx_internal;
	import mx.events.PropertyChangeEvent;
	import mx.events.PropertyChangeEventKind;

	[Bindable]
	public class BasePipelineTask extends BaseTask implements IPipelineTask
	{
		protected var _pipelineData : * = UNDEFINED;
		protected var _inlet : String;
		protected var _outlet : Object = PIPELINE_OUTLET;
		protected var _emptyPipelinePolicy : String = EmptyPipelinePolicy.IGNORE;
		protected var _drillInPath : Array;
		/**
		 * @private
		 */
		protected var _ignoreOutput : Boolean = false;
		
		public function BasePipelineTask() 
		{
			super();
			_outlet = PIPELINE_OUTLET;
		}
		
		/**
		 * contains the current pipelineData value.<br>
		 * This variable is safely usable in the in <code>dataSet</code> event listeners.
		 * It can be used in data binding declarations altough as
		 * binding is called every time any task's pipeline data is set
		 * it would cause potential performance issues and runtime exceptions
		 * Also, keep in mind that $data at the moment is always an ObjectProxy
		 * wrapping the original data. Casting like this: Person( $data ).firstName
		 * will throw an exception. Person( $data.val ).firstName should be used instead.
		 */
		public var $data : *;
		
		override public function begin(inRetain:Boolean=false):void
		{
			if( _pipelineData == EMPTY_PIPELINE && emptyPipelinePolicy == EmptyPipelinePolicy.FAIL )
			{
				fail( "Empty pipeline in task '" + description + "' (" + getQualifiedClassName( this ) + ")" );
				return;
			}
			if( _document is IWorkflowTask )
			{
				_document.$data = new DataWrapper( _pipelineData );
			}
			super.begin( inRetain );
					
		}
		
		/**
		 * after <code>complete()</code> will return this task's pipelineData
		 */
		public function get taskOutput() : *
		{
			return _pipelineData; 
		}
		
		
		public function set taskInput( inData : * ) : void
		{
			if( inData is String && inData.indexOf( "|" ) == 0 )
				_drillInPath = inData.substr(1).split( "." );
			else
				_pipelineData = inData;
		}

		public function get ignoreOutput() : Boolean
		{
			return _ignoreOutput;
		}
		
		public function set ignoreOutput( inIgnoreOutput: Boolean ) : void
		{
			_ignoreOutput = inIgnoreOutput;
		}
		
		/**
		 * @private
		 * 
		 * returns the pipeline data used by this task.<br><br>
		 * If a drill-in path has been defined, pipelineData will return
		 * the drill-in data. Otherwise it will return the raw data.
		 */ 
		protected function get pipelineData() : *
		{
			if( _drillInPath && _pipelineData != UNDEFINED )
			{
				var val : Object = _pipelineData;
				for each( var k : String in _drillInPath )
				{
					val = val[ k ];
				}
				return val;
			}
			else
				return _pipelineData;
		}
		/**
		 * an optional property/function name used to feed data to this task.<br><br>
		 * If specified, the parent workflow will try to inject
		 * the input data either by setting a property:<br>
		 * <code>thisTask[ thisTask.inlet ] = inputData</code><br><br>
		 * or by invoking a one-argument function:<br>
		 * <code>thisTask[ thisTask.inlet ]( inputData )</code><br><br>
		 * depending on what <code>thisTask[ thisTask.inlet ]</code> is<br><br>
		 * Notice that the task's taskInput is set anyway,
		 * even with inlet specified.
		 */
		public function set inlet( inInlet : String ) : void
		{
			_inlet = inInlet;
		}
		public function get inlet() : String
		{
			return _inlet;
		}
		
		/**
		 * the destination of the task's output data.<br><br>
		 * - <code>outlet="$myVar"</code>: send the output data to a context variable<br>
		 * - <code>CLOSED_OUTLET</code>: the output will be ignored (same effect as <code>ignoreOutput="true"</code><br>
		 * - <code>PIPELINE_OUTLET</code> (default): the output will be passed to the parent's pipeline.
		 */
		public function set outlet( inOutlet : Object ) : void
		{
			_outlet = inOutlet;
		}
		
		public function get outlet() : Object
		{
			return _outlet;
		}
		
		/**
		 * determines what to do when the parent's pipeline data 
		 * is <code>EMPTY_PIPELINE</code> <u>before</u> executing this task.<br>
		 * <code>ignore</code> (default): execute the task.<br>
		 * <code>fail</code>: call fail()<br>
		 * <code>skip</code>: ignore this task and go ahead<br>
		 */
		[Inspectable(defaultValue="ignore", enumeration="ignore,skip,fail")]
		public function set emptyPipelinePolicy( inValue : String ) : void
		{
			if( inValue != EmptyPipelinePolicy.FAIL && inValue != EmptyPipelinePolicy.IGNORE && inValue != EmptyPipelinePolicy.SKIP )
			{
				throw new Error(
					"Task 'emptyPipelinePolicy' property can only be either 'ignore', 'fail' or 'skip': " +
					"found '" + inValue + "'" );
			}
			
			_emptyPipelinePolicy= inValue;
		}
		
		public function get emptyPipelinePolicy() : String
		{
			return _emptyPipelinePolicy;
		}
		
		override public function prepare():void
		{
			super.prepare();
			if( parent )
				_pipelineData = UNDEFINED;
		}
		
		override protected function complete():void
		{
			super.complete();
		}
		
	}
}